Skip to content

如何客製化 ASP.NET Core 的 Model Validation 預設錯誤訊息

TLDR

  • ASP.NET Core 預設的 Model Validation 僅提供英文訊息,可透過資源檔(.resx)與 IValidationMetadataProvider 進行客製化。
  • 驗證訊息分為「ModelBinding」與「ValidationMetadata」兩部分,需分別實作設定。
  • 透過 ModelBindingMessageProvider 可直接覆寫 ModelBinding 錯誤訊息。
  • 透過實作 IValidationMetadataProvider 介面,可動態替換 ValidationAttribute 的預設錯誤訊息。
  • 若需支援多國語系,可建立對應語系的資源檔,並透過 RequestLocalizationOptions 設定 SupportedUICultures 來自動切換。
  • 設定語系時應區分 Culture(格式設定)與 UICulture(資源檔載入),錯誤訊息的顯示取決於 UICulture

處理 ModelBinding 與 ValidationMetadata 驗證訊息

在 ASP.NET Core 中,Model Validation 的錯誤訊息來源分為兩類,處理方式如下:

1. 建立資源檔 (.resx)

建立資源檔以存放自定義的錯誤訊息,屬性設定為「內嵌資源」,並根據需求設定存取修飾詞。

  • ModelBindingMessage:處理資料格式相關錯誤(如型別轉換失敗)。
  • ValidationMetadataMessage:處理資料內容驗證錯誤(如 RequiredRange 等屬性)。

2. 客製化 ValidationMetadataProvider

若要替換 ValidationAttribute 的預設錯誤訊息,需實作 IValidationMetadataProvider

什麼情況下會遇到這個問題:當您希望統一修改專案中所有 ValidationAttribute(如 RequiredAttribute)的預設英文錯誤訊息,而不需在每個屬性上重複撰寫 ErrorMessage 時。

csharp
public class LocalizationValidationMetadataProvider : IValidationMetadataProvider {
    private readonly ResourceManager resourceManager;
    private readonly Type resourceType;

    public LocalizationValidationMetadataProvider(Type type) {
        resourceType = type;
        resourceManager = new ResourceManager(type);
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context) {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>()) {
            if (attribute.ErrorMessageResourceName is null) {
                bool hasErrorMessage = attribute.ErrorMessage != null;

                if (hasErrorMessage) {
                    string? defaultErrorMessage = typeof(ValidationAttribute)
                        .GetField("_defaultErrorMessage", BindingFlags.NonPublic | BindingFlags.Instance)
                        ?.GetValue(attribute) as string;

                    hasErrorMessage = attribute.ErrorMessage != defaultErrorMessage;
                }

                if (hasErrorMessage) {
                    continue;
                }

                string? name = GetMessageName(attribute);
                if (name != null && resourceManager.GetString(name) != null) {
                    attribute.ErrorMessageResourceType = resourceType;
                    attribute.ErrorMessageResourceName = name;
                    attribute.ErrorMessage = null;
                }
            }
        }
    }

    private string? GetMessageName(ValidationAttribute attr) {
        switch (attr) {
            case CompareAttribute _:
                return "CompareAttribute_MustMatch";
            case StringLengthAttribute vAttr:
                if (vAttr.MinimumLength > 0) {
                    return "StringLengthAttribute_ValidationErrorIncludingMinimum";
                }
                return "StringLengthAttribute_ValidationError";
            case DataTypeAttribute _:
                return $"{attr.GetType().Name}_Invalid";
            case ValidationAttribute _:
                return $"{attr.GetType().Name}_ValidationError";
        }
        return null;
    }
}

3. 註冊服務

Program.cs 中註冊上述 Provider 並設定 ModelBindingMessageProvider

csharp
builder.Services.AddRazorPages()
    .AddMvcOptions(options => {
        var provider = options.ModelBindingMessageProvider;
        provider.SetAttemptedValueIsInvalidAccessor((x, y) => string.Format(ModelBindingMessage.AttemptedValueIsInvalid, x, y));
        provider.SetMissingBindRequiredValueAccessor(x => string.Format(ModelBindingMessage.MissingBindRequiredValue, x));
        // ... 其他 ModelBinding 訊息設定
        
        options.ModelMetadataDetailsProviders.Add(new LocalizationValidationMetadataProvider(typeof(ValidationMetadataMessage)));
    });

實作多國語系支援

什麼情況下會遇到這個問題:當應用程式需要根據使用者的語系設定,動態顯示對應語言的驗證錯誤訊息時。

設定步驟

  1. 建立對應語系的資源檔,如 ModelBindingMessage.zh-TW.resx
  2. 資源檔屬性設定為「內嵌資源」,且「不要產生程式碼」。
  3. Program.cs 中配置 RequestLocalizationOptions
csharp
WebApplication app = builder.Build();

string[] supportedCultures = new string[] { "zh-TW", "en-US" };
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

INFO

Culture 與 UICulture 的區別

  • Culture:決定日期、數值、貨幣的格式與排序規則。
  • UICulture:決定載入何種語系的資源檔(錯誤訊息顯示依賴此設定)。
  • 若使用 QueryString 傳遞語系,正確參數應為 ui-culture 而非 culture

異動歷程

    • 初版文件建立。
    • 修正 ModelBindingMessage 的訊息。